/*********************************
 * jsmud: JavaScript MUD Kernel  *
 *          Mojodo, Inc.         *
 *        Sean Middleditch       *
 * Copyright (C) 2005,2007,2008  *
 *      All rights reserved.     *
 *   See COPYING for licensing.  *
 *********************************/

/* ==== SYSTEM INCLUDES ==== */

#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/time.h>
#include <time.h>
#include <limits.h>

/* ==== LIBRARY INCLUDES ==== */

#include <sqlite3.h>
#include <zlib.h>

#define XP_UNIX 1
#define MOZILLA_1_8_BRANCH 1
#include <jsapi.h>
#undef XP_UNIX
#undef MOZILLA_1_8_BRANCH

/* ==== JSMUD HEADER ==== */

#include "jsmud.h"

/* ==== GLOBALS ==== */

/* -- Runtime options -- */
const char* opt_log_path = NULL;
const char* opt_data_path = "./mud";
Setting* opt_custom = NULL;

/* -- Logging -- */
FILE* log_file = NULL;
int log_quiet = 0;

/* -- Runtime exit flag -- */
volatile int exit_flag = 0;

/* ==== MAIN ==== */

int main (int argc, char** argv)
{
	fd_set set;
	int max;
	struct sigaction sa;
	struct timeval tv;
	struct timeval last_tv;
	struct timeval result_tv;
	double elapsed;
	char path[PATH_MAX];

	/* Parse arguments */
	if (!parse_argv(argc, argv))
		return 1;

	/* Open log_msg file */
	if (opt_log_path != NULL) {
		FILE* new_log = fopen(opt_log_path, "a");
		if (new_log == NULL) {
			log_error("fopen() failed: %s", strerror(errno));
			return 1;
		}
		log_file = new_log;
	}

	/* Copyright blah blah */
	puts("jsmud: JavaScript MUD Kernel " PACKAGE_VERSION);
	puts("Copyright (C) 2005,2007,2008 Mojodo, Inc.");
	puts("See COPYING for license details.");

	/* Catch signals */
	memset (&sa, 0, sizeof (sa));
	sa.sa_handler = signal_exit_handler;
	if (sigaction (SIGTERM, &sa, NULL)) {
		log_error("sigaction() failed: %s", strerror(errno));
		return 1;
	}
	if (sigaction (SIGINT, &sa, NULL)) {
		log_error("sigaction() failed: %s", strerror(errno));
		return 1;
	}
	if (sigaction (SIGHUP, &sa, NULL)) {
		log_error("sigaction() failed: %s", strerror(errno));
		return 1;
	}

	/* Initialize database */
	if (snprintf(path, sizeof(path), "%s/sqlite3.db", opt_data_path) > sizeof(path)) {
		log_error("cannot open %s/sqlite3.db: path too long", opt_data_path);
		return 1;
	}
	if (sqlite3_open(path, &db_context) != SQLITE_OK) {
		log_error("sqlite3_open() failed for `%s': %s", path, sqlite3_errmsg(db_context));
		return 1;
	}

	/* Initialize JavaScript */
	if (!js_init())
		return 1;

	/* Run initializer function */
	if (!js_main())
		return 1;

	/* Let 'em know we're runnin' */
	log_msg("Ready");

	/* Run main loop */
	gettimeofday(&last_tv, NULL);
	while (!exit_flag && listen_list != NULL) {
		/* run JavaScript GC */
		JS_MaybeGC(js_context);
		
		/* determine time to wait */
		elapsed = timer_next();
		tv.tv_sec = elapsed;
		tv.tv_usec = (elapsed - tv.tv_sec) * 1000.0;
		
		/* select */
		build_fd_set(&set, &max);
		if (select(max + 1, &set, NULL, NULL, elapsed > 0.0 ? &tv : NULL) == -1) {
			if (errno == EINTR)
				continue;
			log_error("select() failed: %s", strerror(errno));
			return 1;
		}

		/* accept server sockets */
		listen_accept(&set);

		/* process clients */
		client_update(&set);

		/* elapsed time, timers */
		gettimeofday(&tv, NULL);
		timersub(&tv, &last_tv, &result_tv);
		last_tv = tv;
		elapsed = result_tv.tv_sec + (result_tv.tv_usec / 1000000.0);
		timer_update(elapsed);

		/* flush client output */
		client_flush();
	}

	log_msg("Shutting down");

	/* Clean up */
	listen_free_all();
	client_free_all();
	timer_free_all();
	zmp_free_all();

	/* Run shutdown function */
	js_call_method(js_global, "shutdown", 0, NULL);

	/* Shutdown JavaScript */
	JS_ClearScope(js_context, js_global);
	JS_DestroyContext(js_context);
	JS_DestroyRuntime(js_runtime);
	JS_ShutDown();

	/* Close database */
	if (sqlite3_close(db_context) != SQLITE_OK) {
		log_error("sqlite3_close() failed: %s", sqlite3_errmsg(db_context));
		return 1;
	}

	return 0;
}

/* ==== FUNCTIONS ==== */

/* -- Logging -- */
void log_time (FILE* file)
{
	time_t t;
	const char* string;

	time(&t);
	string = ctime(&t);

	fprintf(file, "%.*s - ", (int)strlen(string) - 1, string);
}

void log_msg (const char* format, ...)
{
	va_list va;

	/* log to log file */
	if (log_file != NULL) {
		log_time(log_file);
		va_start(va, format);
		vfprintf(log_file, format, va);
		fprintf(log_file, "\n");
		va_end(va);
	}

	/* log to stdout if we have no log file or if quiet mode is off */
	if (stdout == NULL || !log_quiet) {
		va_start(va, format);
		vfprintf(stdout, format, va);
		fprintf(stdout, "\n");
		va_end(va);
	}
}

/* -- Error Handling -- */
void log_error (const char* format, ...)
{
	va_list va;

	/* log to the log file */
	if (log_file != NULL) {
		log_time(log_file);
		va_start(va, format);
		fprintf(log_file, "Fatal error: ");
		vfprintf(log_file, format, va);
		fprintf(log_file, "\n");
		va_end(va);
	}

	/* always log to stderr */
	va_start(va, format);
	fprintf(stderr, "Fatal error: ");
	vfprintf(stderr, format, va);
	fprintf(stderr, "\n");
	va_end(va);

	exit (1);
}

/* -- Argument parsing -- */
int parse_argv (int argc, char** argv)
{
	char* name;
	char* value;
	int i;

	for (i = 1; i < argc; ++i) {
		/* long option format */
		if (!strncmp(argv[i], "--", 2)) {
			name = argv[i] + 2;
			value = strchr(name, '=');
			if (value != NULL) {
				*value = 0;
				++value;
			} else {
				value = "";
			}

			/* database path */
			if (!strcmp(name, "data")) {
				opt_data_path = value;
			/* logfile path */
			} else if (!strcmp(name, "log")) {
				opt_log_path = value;
			/* custom option */
			} else {
				Setting* setting = (Setting*)malloc(sizeof(Setting));
				if (setting == NULL) {
					log_error("malloc() failed: %s", strerror(errno));
					return 0;
				}

				setting->name = name;
				setting->value = value;
				setting->next = opt_custom;
				opt_custom = setting;
			}
		/* short option format */
		} else if (strlen(argv[i]) == 2 && argv[i][0] == '-') {
			switch (argv[i][1]) {
				/* quiet */
				case 'q':
					log_quiet = 1;
					break;
				/* unknown short option */
				default:
					log_error("Invalid argument: %s", argv[i]);
					return 0;
			}
		/* error */
		} else {
			log_error("Invalid argument: %s", argv[i]);
			return 0;
		}
	}

	return 1;
}

/* -- Terminating signal handler -- */
void signal_exit_handler (int signal)
{
	exit_flag = 1;
}

/* vim: set tabstop=2 shiftwidth=2 ai: */
